﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;
using Tesla.Math;
using Tesla.Bounding;

namespace Tesla.Net {
    public static class TeslaExtensions {

        #region Write Methods

        public static void Write(this NetOutgoingMessage message, Vector4 vector) {
            message.Write(vector.X);
            message.Write(vector.Y);
            message.Write(vector.Z);
            message.Write(vector.W);
        }

        public static void Write(this NetOutgoingMessage message, ref Vector4 vector) {
            message.Write(vector.X);
            message.Write(vector.Y);
            message.Write(vector.Z);
            message.Write(vector.W);
        }

        public static void Write(this NetOutgoingMessage message, Vector3 vector) {
            message.Write(vector.X);
            message.Write(vector.Y);
            message.Write(vector.Z);
        }

        public static void Write(this NetOutgoingMessage message, ref Vector3 vector) {
            message.Write(vector.X);
            message.Write(vector.Y);
            message.Write(vector.Z);
        }

        public static void Write(this NetOutgoingMessage message, Vector2 vector) {
            message.Write(vector.X);
            message.Write(vector.Y);
        }

        public static void Write(this NetOutgoingMessage message, ref Vector2 vector) {
            message.Write(vector.X);
            message.Write(vector.Y);
        }

        public static void Write(this NetOutgoingMessage message, Quaternion quaternion) {
            message.Write(quaternion.X);
            message.Write(quaternion.Y);
            message.Write(quaternion.Z);
            message.Write(quaternion.W);
        }

        public static void Write(this NetOutgoingMessage message, ref Quaternion quaternion) {
            message.Write(quaternion.X);
            message.Write(quaternion.Y);
            message.Write(quaternion.Z);
            message.Write(quaternion.W);
        }

        public static void Write(this NetOutgoingMessage message, Color color) {
            message.Write(color.PackedValue);
        }

        public static void Write(this NetOutgoingMessage message, ref Color color) {
            message.Write(color.PackedValue);
        }

        public static void Write(this NetOutgoingMessage message, Plane plane) {
            message.Write(plane.D);
            Vector3 norm = plane.Normal;
            message.Write(ref norm);
        }

        public static void Write(this NetOutgoingMessage message, ref Plane plane) {
            message.Write(plane.D);
            Vector3 norm = plane.Normal;
            message.Write(ref norm);
        }

        public static void Write(this NetOutgoingMessage message,  Point point) {
            message.Write(point.X);
            message.Write(point.Y);
        }

        public static void Write(this NetOutgoingMessage message, ref Point point) {
            message.Write(point.X);
            message.Write(point.Y);
        }

        public static void Write(this NetOutgoingMessage message, Ray ray) {
            message.Write(ray.Origin);
            message.Write(ray.Direction);
        }

        public static void Write(this NetOutgoingMessage message, ref Ray ray) {
            message.Write(ray.Origin);
            message.Write(ray.Direction);
        }

        public static void Write(this NetOutgoingMessage message, Rectangle rectangle) {
            message.Write(rectangle.X);
            message.Write(rectangle.Y);
            message.Write(rectangle.Width);
            message.Write(rectangle.Height);
        }

        public static void Write(this NetOutgoingMessage message, ref Rectangle rectangle) {
            message.Write(rectangle.X);
            message.Write(rectangle.Y);
            message.Write(rectangle.Width);
            message.Write(rectangle.Height);
        }

        public static void Write(this NetOutgoingMessage message, Triangle triangle) {
            message.Write(triangle.PointA);
            message.Write(triangle.PointB);
            message.Write(triangle.PointC);
        }

        public static void Write(this NetOutgoingMessage message, ref Triangle triangle) {
            message.Write(triangle.PointA);
            message.Write(triangle.PointB);
            message.Write(triangle.PointC);
        }

        public static void Write(this NetOutgoingMessage message, Matrix matrix) {
            message.Write(matrix.Up);
            message.Write(matrix.Backward);
            message.Write(matrix.Right);
            message.Write(matrix.Translation);
        }

        public static void Write(this NetOutgoingMessage message, ref Matrix matrix) {
            message.Write(matrix.Up);
            message.Write(matrix.Backward);
            message.Write(matrix.Right);
            message.Write(matrix.Translation);
        }

        public static void Write(this NetOutgoingMessage message, Transform transform) {
            message.Write(transform.Scale);
            message.Write(transform.Rotation);
            message.Write(transform.Translation);
        }

        public static void Write(this NetOutgoingMessage message, BoundingVolume volume) {
            switch(volume.BoundingType) {
                case BoundingType.AABB:
                    Write(message, volume as BoundingBox);
                    break;
                case BoundingType.OBB:
                    Write(message, volume as OrientedBoundingBox);
                    break;
                case BoundingType.Sphere:
                    Write(message, volume as BoundingSphere);
                    break;
            }
        }

        public static void Write(this NetOutgoingMessage message, BoundingBox volume) {
            message.Write((byte) volume.BoundingType);
            message.Write(volume.Center);
            message.Write(volume.Extents);
        }

        public static void Write(this NetOutgoingMessage message, BoundingSphere volume) {
            message.Write((byte) volume.BoundingType);
            message.Write(volume.Center);
            message.Write(volume.Radius);
        }

        public static void Write(this NetOutgoingMessage message, OrientedBoundingBox volume) {
            message.Write((byte) volume.BoundingType);
            message.Write(volume.Center);
            message.Write(volume.XAxis);
            message.Write(volume.YAxis);
            message.Write(volume.ZAxis);
            message.Write(volume.Extents);
        }

        #endregion

        #region Read Methods

        public static Vector4 ReadVector4(this NetIncomingMessage message) {
            Vector4 v;
            v.X = message.ReadFloat();
            v.Y = message.ReadFloat();
            v.Z = message.ReadFloat();
            v.W = message.ReadFloat();
            return v;
        }

        public static void ReadVector4(this NetIncomingMessage message, out Vector4 result) {
            result.X = message.ReadFloat();
            result.Y = message.ReadFloat();
            result.Z = message.ReadFloat();
            result.W = message.ReadFloat();
        }

        public static Vector3 ReadVector3(this NetIncomingMessage message) {
            Vector3 v;
            v.X = message.ReadFloat();
            v.Y = message.ReadFloat();
            v.Z = message.ReadFloat();
            return v;
        }

        public static void ReadVector3(this NetIncomingMessage message, out Vector3 result) {
            result.X = message.ReadFloat();
            result.Y = message.ReadFloat();
            result.Z = message.ReadFloat();
        }

        public static Vector2 ReadVector2(this NetIncomingMessage message) {
            Vector2 v;
            v.X = message.ReadFloat();
            v.Y = message.ReadFloat();
            return v;
        }

        public static void ReadVector2(this NetIncomingMessage message, out Vector2 result) {
            result.X = message.ReadFloat();
            result.Y = message.ReadFloat();
        }

        public static Quaternion ReadQuaternion(this NetIncomingMessage message) {
            Quaternion q;
            q.X = message.ReadFloat();
            q.Y = message.ReadFloat();
            q.Z = message.ReadFloat();
            q.W = message.ReadFloat();
            return q;
        }

        public static void ReadQuaternion(this NetIncomingMessage message, out Quaternion result) {
            result.X = message.ReadFloat();
            result.Y = message.ReadFloat();
            result.Z = message.ReadFloat();
            result.W = message.ReadFloat();
        }

        public static Color ReadColor(this NetIncomingMessage message) {
            return new Color(message.ReadUInt32());
        }

        public static void ReadColor(this NetIncomingMessage message, out Color result) {
            result = new Color(message.ReadUInt32());
        }

        public static Plane ReadPlane(this NetIncomingMessage message) {
            Plane p;
            p.D = message.ReadFloat();
            message.ReadVector3(out p.Normal);
            return p;
        }

        public static void ReadPlane(this NetIncomingMessage message, out Plane result) {
            result.D = message.ReadFloat();
            message.ReadVector3(out result.Normal);
        }

        public static Point ReadPoint(this NetIncomingMessage message) {
            Point p;
            p.X = message.ReadInt32();
            p.Y = message.ReadInt32();
            return p;
        }

        public static void ReadPoint(this NetIncomingMessage message, out Point result) {
            result.X = message.ReadInt32();
            result.Y = message.ReadInt32();
        }

        public static Ray ReadRay(this NetIncomingMessage message) {
            Ray r;
            message.ReadVector3(out r.Origin);
            message.ReadVector3(out r.Direction);
            return r;
        }

        public static void ReadRay(this NetIncomingMessage message, out Ray result) {
            message.ReadVector3(out result.Origin);
            message.ReadVector3(out result.Direction);
        }

        public static Rectangle ReadRectangle(this NetIncomingMessage message) {
            Rectangle rect;
            rect.X = message.ReadInt32();
            rect.Y = message.ReadInt32();
            rect.Width = message.ReadInt32();
            rect.Height = message.ReadInt32();
            return rect;
        }

        public static void ReadRectangle(this NetIncomingMessage message, out Rectangle result) {
            result.X = message.ReadInt32();
            result.Y = message.ReadInt32();
            result.Width = message.ReadInt32();
            result.Height = message.ReadInt32();
        }

        public static Triangle ReadTriangle(this NetIncomingMessage message) {
            Vector3 a, b, c;
            message.ReadVector3(out a);
            message.ReadVector3(out b);
            message.ReadVector3(out c);
            return new Triangle(a, b, c);
        }

        public static void ReadTriangle(this NetIncomingMessage message, out Triangle result) {
            Vector3 a, b, c;
            message.ReadVector3(out a);
            message.ReadVector3(out b);
            message.ReadVector3(out c);
            result = new Triangle(a, b, c);
        }

        public static Matrix ReadMatrix(this NetIncomingMessage message) {
            Vector3 up, backward, right;
            message.ReadVector3(out up);
            message.ReadVector3(out backward);
            message.ReadVector3(out right);

            Matrix m = Matrix.Identity;
            m.Up = up;
            m.Backward = backward;
            m.Right = right;
            return m;
        }

        public static void ReadMatrix(this NetIncomingMessage message, out Matrix result) {
            Vector3 up, backward, right;
            message.ReadVector3(out up);
            message.ReadVector3(out backward);
            message.ReadVector3(out right);

            result = Matrix.Identity;
            result.Up = up;
            result.Backward = backward;
            result.Right = right;
        }

        public static Transform ReadTransform(this NetIncomingMessage message) {
            Vector3 scale, translate;
            Quaternion rotate;
            message.ReadVector3(out scale);
            message.ReadQuaternion(out rotate);
            message.ReadVector3(out translate);

            Transform transform = new Transform(scale, rotate, translate);
            return transform;
        }

        public static void ReadTransform(this NetIncomingMessage message, Transform existing) {
            Vector3 scale, translate;
            Quaternion rotate;
            message.ReadVector3(out scale);
            message.ReadQuaternion(out rotate);
            message.ReadVector3(out translate);

            if(existing != null) {
                existing.Set(scale, rotate, translate);
            }
        }

        public static BoundingVolume ReadBoundingVolume(this NetIncomingMessage message) {
            BoundingType type = (BoundingType) message.ReadByte();
            Vector3 center;
            message.ReadVector3(out center);
            switch(type) {
                case BoundingType.AABB:
                    Vector3 aabbExtents;
                    message.ReadVector3(out aabbExtents);
                    return new BoundingBox(center, aabbExtents);
                case BoundingType.Sphere:
                    return new BoundingSphere(center, message.ReadFloat());
                case BoundingType.OBB:
                    Vector3 xAxis, yAxis, zAxis, obbExtents;
                    message.ReadVector3(out xAxis);
                    message.ReadVector3(out yAxis);
                    message.ReadVector3(out zAxis);
                    message.ReadVector3(out obbExtents);
                    return new OrientedBoundingBox(center, xAxis, yAxis, zAxis, obbExtents);
                default:
                    return null;
            }
        }

        public static BoundingBox ReadBoundingBox(this NetIncomingMessage message) {
            BoundingType type = (BoundingType) message.ReadByte();
            Vector3 center;
            Vector3 extents;

            message.ReadVector3(out center);
            message.ReadVector3(out extents);
            return new BoundingBox(center, extents);
        }

        public static void ReadBoundingBox(this NetIncomingMessage message, BoundingBox existing) {
            BoundingType type = (BoundingType) message.ReadByte();
            Vector3 center;
            Vector3 extents;

            message.ReadVector3(out center);
            message.ReadVector3(out extents);
            if(existing != null) {
                existing.Set(center, extents);
            }
        }

        public static BoundingSphere ReadBoundingSphere(this NetIncomingMessage message) {
            BoundingType type = (BoundingType) message.ReadByte();
            Vector3 center;
            message.ReadVector3(out center);
            float radius = message.ReadFloat();

            return new BoundingSphere(center, radius);
        }

        public static void ReadBoundingSphere(this NetIncomingMessage message, BoundingSphere existing) {
            BoundingType type = (BoundingType) message.ReadByte();
            Vector3 center;
            message.ReadVector3(out center);
            float radius = message.ReadFloat();

            if(existing != null) {
                existing.Set(center, radius);
            }
        }

        public static OrientedBoundingBox ReadOrientedBoundingBox(this NetIncomingMessage message) {
            BoundingType type = (BoundingType) message.ReadByte();
            Vector3 center, xAxis, yAxis, zAxis, extents;

            message.ReadVector3(out center);
            message.ReadVector3(out xAxis);
            message.ReadVector3(out yAxis);
            message.ReadVector3(out zAxis);
            message.ReadVector3(out extents);

            return new OrientedBoundingBox(center, xAxis, yAxis, zAxis, extents);
        }

        public static void ReadOrientedBoundingBox(this NetIncomingMessage message, OrientedBoundingBox existing) {
            BoundingType type = (BoundingType) message.ReadByte();
            Vector3 center, xAxis, yAxis, zAxis, extents;

            message.ReadVector3(out center);
            message.ReadVector3(out xAxis);
            message.ReadVector3(out yAxis);
            message.ReadVector3(out zAxis);
            message.ReadVector3(out extents);

            if(existing != null) {
                existing.Set(center, xAxis, yAxis, zAxis, extents);
            }
        }

        #endregion
    }
}
